#include "dbuscaller.hpp"
#include "common.hpp"
#include <chrono>
#include "localmethod.hpp"
DbusCaller::DbusCaller(QObject *parent) : QObject(parent)
{
    m_runStates = 0;
    m_thread = NULL;
    //    connect (&m_thread,&QThread::started,this,&DbusCaller::onCall);
    //    connect (this,&DbusCaller::setSignal,this,&DbusCaller::onSignal);
    m_CallTimeoutMs = 3000;
}

bool DbusCaller::start(QVector<DbusTestModelItem *> list)
{
    if(!isRunning())
    {
        if(m_thread!=NULL)
        {
            m_thread->join ();
            delete m_thread;
        }
        m_runRow = -1;
        m_list = list;
        //        foreach (DbusTestModelItem *var, m_list) {
        //            var->m_result = "";
        //            var->m_starttime = 0;
        //            var->m_finshtime = 0;
        //        }
        m_stop = 0;
        m_loop = 0;
        m_loop_times = 0;
        m_thread = new std::thread(&DbusCaller::onCall, this);
        //        m_thread.start ();
        return true;
    }
    return false;
}

bool DbusCaller::start(DbusTestModelItem *item, int row)
{
    if(m_thread!=NULL)
    {
        if(m_runStates>2)
        {
            return false;
        }
    }
    if(!isRunning())
    {
        if(m_thread!=NULL)
        {
            m_thread->join ();
            delete m_thread;
        }
        m_runRow = row;
        m_list.clear ();
        m_list<<item;
        //        foreach (DbusTestModelItem *var, m_list) {
        //            var->m_result = "";
        //            var->m_starttime = 0;
        //            var->m_finshtime = 0;
        //        }
        m_stop = 0;
        m_loop = 0;
        m_loop_times = 0;
        m_thread = new std::thread(&DbusCaller::onCall, this);
        return true;
    }
    return false;
}

bool DbusCaller::loop(QVector<DbusTestModelItem *> list, int times)
{
    if(m_thread!=NULL)
    {
        if(m_runStates>2)
        {
            return false;
        }
    }
    if(!isRunning())
    {
        if(m_thread!=NULL)
        {
            m_thread->join ();
            delete m_thread;
        }
        m_runRow = -1;
        m_list = list;
        //        foreach (DbusTestModelItem *var, m_list) {
        //            var->m_result = "";
        //            var->m_starttime = 0;
        //            var->m_finshtime = 0;
        //        }
        m_stop = 0;
        m_loop = 1;
        m_loop_times = times;
        m_thread = new std::thread(&DbusCaller::onCall, this);
        return true;
    }
    return false;
}

void DbusCaller::stop()
{
    m_stop = 1;
}

bool DbusCaller::isFinished() const
{
    return m_runStates == 0;
}

bool DbusCaller::isRunning() const
{
    return m_runStates == 2;
}


void DbusCaller::onCall()
{
    m_runStates = 2;
//    qDebug()<<"run seq start";
    m_loop_times = 0;
    emit onStart();
    while (true) {
        m_loop_times++;
        foreach (DbusTestModelItem *var, m_list) {
            var->setResult("");
            var->m_starttime = 0;
            var->m_finshtime = 0;
        }
        emit onResetResult(m_runRow);
        for(int i=0;i<m_list.count ();i++)
        {
            if(m_stop != 0)
            {
                break;
            }
            DbusTestModelItem *item = m_list.at (i);
            if(!item->m_skip)
            {
                if(m_runRow>0)
                {
                    callItem(item,m_runRow);
                }else
                {
                    callItem(item,i);
                }
            }

        }
        if(m_loop == 0)
            break;

        if(m_loop_stop_times>0)
        {
            if(m_loop_stop_times<m_loop_times)
                break;
        }
        if(m_stop != 0)
        {
            break;
        }
    }
    m_runStates = 0;
//    qDebug()<<"run seq exited";
    emit onFinish ();
}

void DbusCaller::signalCall(const QDBusMessage &msg)
{
    m_signalMsg = msg;
    m_cond.wakeOne ();

}


QDBusMessage DbusCaller::callItem(DbusTestModelItem *item, int row)
{
    emit onItemStart(item,row);

    QDBusConnection bus("NONE");
    if(item->m_data.type !=  LocalMethod){
        if(item->m_data.mBus == DBUS_NAME_SESSION)
        {
            bus = QDBusConnection::connectToBus (QDBusConnection::SessionBus,DBUS_NAME_SESSION);
        }else if(item->m_data.mBus == DBUS_NAME_SYSTEM)
        {
            bus = QDBusConnection::connectToBus (QDBusConnection::SystemBus,DBUS_NAME_SYSTEM);
        }else
        {
            bus = QDBusConnection::connectToBus (item->m_data.mBus,item->m_data.mBus);
        }
        if(!bus.isConnected ())
        {
            emit onError (item,row,"DBus connect fail");
            QDBusMessage msg = QDBusMessage::createError (QDBusError::NoNetwork,"DBus connect fail");
            QVariant ret = QVariant::fromValue(msg);
            emit onItemFinish1(item,row,m_loop_times,ret);
            return msg;
        }
    }
    QDBusMessage msg;
    if(item->m_data.type == MethodItem)
    {
        msg = callMethod (item,bus);
    }
    else if(item->m_data.type ==  PropertyItemReadwrite
            || item->m_data.type ==  PropertyItemRead
            || item->m_data.type ==  PropertyItemWrite )
    {
        if(item->dbusArgs.count ()>0)
        {
            msg = setProperty (item,bus);
        }else
        {
            msg = getProperty (item,bus);
        }
    }else if(item->m_data.type ==  SignalItem)
    {
        msg = waitSignal(item,bus);
    }else if(item->m_data.type ==  LocalMethod)
    {
        bool ok=false;
        QVariant ret=calllocalMethod(item,&ok);
        if(!ok)
        {
            item->m_error = 1;
            QString str = QString("error local method:%1").arg (item->m_data.mName);
            emit onError (item,row,str);
        }else
        {
            item->m_error = 0;
        }
        emit onItemFinish1(item,row,m_loop_times,ret);
    }
    if(msg.type () == QDBusMessage::ErrorMessage)
    {
        emit onError (item,row,msg.errorMessage ());
    }
    QVariant as = QVariant::fromValue(msg);
    emit onItemFinish1(item,row,m_loop_times,as);
    return msg;

}

QDBusMessage DbusCaller::waitSignal(DbusTestModelItem *item, QDBusConnection bus)
{
    QString signature="";
    foreach (auto var, item->m_data.argTypes) {
        signature+=var;
    }
    QString service = item->m_data.mService;
    QString path = item->m_data.mPath;
    QString interface = item->m_data.mInterface;
    QString name = item->m_data.mName;
    int timeout = 1000;
    bool ok=false;
    if(item->dbusArgs.count ()>0)
    {
        timeout = item->dbusArgs.first ().getInt32 (&ok);
    }
    if(!ok)
    {
        timeout = 1000;
    }
    bool b = bus.connect(service,path,interface,name, signature,this, SLOT(signalCall(QDBusMessage)));
    m_mutex.lock ();
    bool bRet = m_cond.wait (&m_mutex,timeout);
    m_mutex.unlock ();
    bus.disconnect(service,path,interface,name, signature,this, SLOT(signalCall(QDBusMessage)));
    if(bRet)
    {
        return m_signalMsg;
    }else
    {
        QDBusMessage msg = QDBusMessage::createError (QDBusError::Timeout,"Wait for Signal fail");
        return msg;
    }
}
int DbusCaller::getCallTimeoutMs() const
{
    return m_CallTimeoutMs;
}

void DbusCaller::setCallTimeoutMs(int CallTimeoutMs)
{
    m_CallTimeoutMs = CallTimeoutMs;
}

QVariant DbusCaller::calllocalMethod(DbusTestModelItem *item,bool *ok)
{
    QList<QVariant> argumentList;
    for (int i = 0; i < item->dbusArgs.count(); ++i) {
        QDBusArgument dbus_arg;
        DVariant arg = item->dbusArgs.at (i);
        argumentList<<arg.m_Values;
    }
    QVariant v=localObj().callMethod (item->m_data.mName,argumentList,ok);
    return v;
}

QDBusMessage DbusCaller::callMethod(DbusTestModelItem *item, QDBusConnection bus)
{
    QList<QVariant> argumentList;
    for (int i = 0; i < item->dbusArgs.count(); ++i) {
        QDBusArgument dbus_arg;
        DVariant arg = item->dbusArgs.at (i);
        arg.toQDBusArgument (dbus_arg);
        argumentList<<QVariant::fromValue(dbus_arg);
    }
    QDBusMessage message = QDBusMessage::createMethodCall(item->m_data.mService, item->m_data.mPath, item->m_data.mInterface,
                                                          item->m_data.mName);
    message.setArguments(argumentList);
    QDBusMessage msg = bus.call (message,QDBus::Block,m_CallTimeoutMs);
    return msg;
}
QDBusMessage DbusCaller::getProperty(DbusTestModelItem *item,QDBusConnection bus)
{
    QDBusMessage message = QDBusMessage::createMethodCall(item->m_data.mService, item->m_data.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get"));
    QList<QVariant> arguments;
    arguments << item->m_data.mInterface << item->m_data.mName;
    message.setArguments(arguments);
    QDBusMessage msg = bus.call (message,QDBus::Block,m_CallTimeoutMs);
    return msg;
}

QDBusMessage DbusCaller::setProperty(DbusTestModelItem *item, QDBusConnection bus)
{
    QDBusMessage message = QDBusMessage::createMethodCall(item->m_data.mService, item->m_data.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Set"));
    QList<QVariant> arguments;
    arguments<<item->m_data.mInterface;
    arguments<<item->m_data.mName;
    QDBusVariant value;
    if(item->dbusArgs.count ()<1)
    {
        QDBusMessage msg = QDBusMessage::createError (QDBusError::InvalidArgs,"InvalidArgs");
        return msg;
    }
    DVariant arg = item->dbusArgs.first ();
    QVariant var = arg.m_Values;
    if(!var.convert (arg.type ()))
    {
        QDBusMessage msg = QDBusMessage::createError (QDBusError::InvalidArgs,"InvalidArgs");
        return msg;
    }
    value.setVariant (var);
    arguments<<QVariant::fromValue(value);
    message.setArguments(arguments);
    QDBusMessage msg = bus.call (message,QDBus::Block,m_CallTimeoutMs);
    return msg;
}
